#include "config.h"
#include "video_manager.h"
#include "disp_manager.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>

//static int g_aiSupportedFormats[] = {V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_RGB565};

static int g_aiSupportedFormats[] = {V4L2_PIX_FMT_RGB565};  /*程序支持的图像格式*/

static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static T_VideoOpr g_tV4l2VideoOpr;             /***/

/*
 * @description		       : 判断是否支持该格式
 * @param - iPixelFormat   : 图像格式，videodev2.h中定义
 * @return 			       : 0 成功;其他 失败
 */
static int isSupportThisFormat(uint32_t iPixelFormat)
{
    int i;
    for (i = 0; i < sizeof(g_aiSupportedFormats)/sizeof(g_aiSupportedFormats[0]); i++)
    {
        if (g_aiSupportedFormats[i] == iPixelFormat)
            return 1;
    }
    return 0;
}
/* 
 * V4L2步骤：
 * open                                                                 对应结构体
 * VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)  struct v4l2_capability *cap
 * VIDIOC_ENUM_FMT 查询支持哪种格式                                        struct v4l2_fmtdesc *fmtdesc
 * VIDIOC_S_FMT    设置摄像头使用哪种格式                                   struct v4l2_format *fmt
 * VIDIOC_REQBUFS  申请buffer                                            struct v4l2_requestbuffers*reqbuf
 对于 streaming:
 * VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap                         struct v4l2_buffer *buf
 * VIDIOC_QBUF     放入队列
 * VIDIOC_STREAMON 启动设备
 * poll            等待有数据
 * VIDIOC_DQBUF    从队列中取出
 * 处理....
 * VIDIOC_QBUF     放入队列
 * ....
 对于read,write:
    read
    处理....
    read
 * VIDIOC_STREAMOFF 停止设备
 */
/*
 * @description		       : 初始化指定Video设备
 * @param - strDevName     : Video设备文件节点
 * @param - ptVideoDevice  : 设备描述结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2InitDevice(char *strDevName, PT_VideoDevice ptVideoDevice)
{
    int i;
    int iFd;
    int iError;
    struct v4l2_capability tV4l2Cap;              /*设备支持的功能*/         
	struct v4l2_fmtdesc tFmtDesc;                 /*设备支持的图像格式*/
    struct v4l2_format  tV4l2Fmt;                 /*设置设备的帧格式等参数*/
    struct v4l2_requestbuffers tV4l2ReqBuffs;     /*描述了需要申请帧缓冲的信息*/
    struct v4l2_buffer tV4l2Buf;                  /*描述实际申请到的帧缓冲信息*/
    /* LCD分辨率 */
    int iLcdWidth;                                
    int iLcdHeigt;
    int iLcdBpp;
    /*打开摄像头*/
    iFd = open(strDevName, O_RDWR);
    if (iFd < 0)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"can not open %s\n", strDevName);
        return -1;
    }
    ptVideoDevice->iFd = iFd;

    memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
    /*查询设备功能*/
    iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
    if (iError) {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Error opening device %s: unable to query device.\n", strDevName);
    	goto err_exit;
    }
    /*是否支持视频采集*/
    if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"%s is not a video capture device\n", strDevName);
        goto err_exit;
    }
    /*是否支持流传输*/
	if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
	    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"%s supports streaming i/o\n", strDevName);
	}
    /*是否支持读写操作*/
	if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
	    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"%s supports read i/o\n", strDevName);
	}

	memset(&tFmtDesc, 0, sizeof(tFmtDesc));
	tFmtDesc.index = 0;                           /*索引初始化为0*/
	tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  /*视频捕获*/
    /*获取设备支持的格式*/
    while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0)
    {
        if (isSupportThisFormat(tFmtDesc.pixelformat))
        {
            /*设置设备格式*/
            ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
            break;
        }
        tFmtDesc.index++;
    }
    /*当前程序不支持该设备格式*/
    if (!ptVideoDevice->iPixelFormat)
    {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"can not support the format of this device\n");
        goto err_exit;        
    }
 
    /* 设置设备帧格式等 */
    GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
    memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
    tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                  /*视频采集*/
    tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;   /*图像数据格式 565....*/
    tV4l2Fmt.fmt.pix.width       = iLcdWidth;                     /*图像数据宽*/
    tV4l2Fmt.fmt.pix.height      = iLcdHeigt;                     /*图像数据高*/
    tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;                /*帧的扫描方式*/

    /* 如果驱动程序发现无法设置某些参数(比如分辨率),它会调整这些参数, 并且返回给应用程序*/
    /*设置设备参数*/
    iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); 
    if (iError) 
    {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to set format\n");
        goto err_exit;        
    }
    /*实际的图像分辨率*/
    ptVideoDevice->iWidth  = tV4l2Fmt.fmt.pix.width;
    ptVideoDevice->iHeight = tV4l2Fmt.fmt.pix.height;

    /* 设置缓冲区信息*/
    memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
    tV4l2ReqBuffs.count = NB_BUFFER;
    tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;                /*使用内存映射的方式来处理视频帧*/
    /*申请帧缓冲区*/
    iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
    if (iError) 
    {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to allocate buffers.\n");
        goto err_exit;        
    }
    /*申请到的缓冲区个数*/
    ptVideoDevice->iVideoBufCnt = tV4l2ReqBuffs.count;
    /*如果是STREAMING I/O方式*/
    if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING)
    {
        /* 将帧缓冲区mmap到进程地址空间 */
        for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) 
        {
        	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        	tV4l2Buf.index = i;
        	tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        	tV4l2Buf.memory = V4L2_MEMORY_MMAP;               /*使用内存映射的方式来处理视频帧*/
            /*映射之前，需要查询帧缓冲的信息*/
        	iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
        	if (iError) 
            {
        	    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to query buffer.\n");
        	    goto err_exit;
        	}

            ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;            /*帧缓冲的长度*/
            /*获取首地址*/
        	ptVideoDevice->pucVideBuf[i] = mmap(NULL,tV4l2Buf.length, PROT_READ, MAP_SHARED,iFd,tV4l2Buf.m.offset);
        	if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED) 
            {
        	    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to map buffer\n");
        	    goto err_exit;
        	}
        }        

        /* 帧缓冲放入内核帧缓冲区队列中 */
        for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) 
        {
        	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        	tV4l2Buf.index = i;
        	tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
        	iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
        	if (iError)
            {
        	    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to queue buffer.\n");
        	    goto err_exit;
        	}
        }
        
    }
    else if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE)  /*如果是READWRITE I/O方式*/
    {
        g_tV4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;
        g_tV4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;
        
        /* read(fd, buf, size) */
        ptVideoDevice->iVideoBufCnt  = 1;
        /* 在这个程序所能支持的格式里, 一个象素最多只需要4，即RGB565字节 */
        ptVideoDevice->iVideoBufMaxLen = ptVideoDevice->iWidth * ptVideoDevice->iHeight * 2;
        ptVideoDevice->pucVideBuf[0] = malloc(ptVideoDevice->iVideoBufMaxLen);
    }

    ptVideoDevice->ptOPr = &g_tV4l2VideoOpr;
    return 0;
    
err_exit:    
    close(iFd);
    return -1;    
}
/*
 * @description		       : 关闭Video设备
 * @param - ptVideoDevice  : 设备描述结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2ExitDevice(PT_VideoDevice ptVideoDevice)
{
    int i;
    for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
    {
        if (ptVideoDevice->pucVideBuf[i])
        {
            munmap(ptVideoDevice->pucVideBuf[i], ptVideoDevice->iVideoBufMaxLen);
            ptVideoDevice->pucVideBuf[i] = NULL;
        }
    }
        
    close(ptVideoDevice->iFd);
    return 0;
}
/*
 * @description		       : 从内核缓冲区队列中取出一帧帧缓冲
 * @param - ptVideoDevice  : 设备描述结构体
 * @param - ptVideoBuf     : Video图像数据结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    struct pollfd tFds[1];
    int iRet;
    struct v4l2_buffer tV4l2Buf;
            
    /* 设置poll操作 */
    tFds[0].fd     = ptVideoDevice->iFd;
    tFds[0].events = POLLIN;

    iRet = poll(tFds, 1, -1);
    if (iRet <= 0)
    {
        DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"poll error!\n");
        return -1;
    }
    
    /* VIDIOC_DQBUF */
    memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);
    if (iRet < 0) 
    {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to dequeue buffer.\n");
    	return -1;
    }
    /* 得到图像数据 */
    ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index;

    ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;
    ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;
    ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;
    ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 :  \
                                        0;
    ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;
    ptVideoBuf->tPixelDatas.iTotalBytes   = tV4l2Buf.bytesused;
    ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index];    
    return 0;
}

/*
 * @description		       : 当前帧缓冲放入内核缓冲区队列中
 * @param - ptVideoDevice  : 设备描述结构体
 * @param - ptVideoBuf     : Video图像数据结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    /* VIDIOC_QBUF */
    struct v4l2_buffer tV4l2Buf;
    int iError;
    
	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
	tV4l2Buf.index  = ptVideoDevice->iVideoBufCurIndex;
	tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    /*将当前帧缓冲放入内核缓冲区队列中*/
	iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf);
	if (iError) 
    {
	    DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to queue buffer.\n");
	    return -1;
	}
    return 0;
}
/*
 * @description		       : 读写方式读取一帧图像数据
 * @param - ptVideoDevice  : 设备描述结构体
 * @param - ptVideoBuf     : Video图像数据结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    int iRet;

    iRet = read(ptVideoDevice->iFd, ptVideoDevice->pucVideBuf[0], ptVideoDevice->iVideoBufMaxLen);
    if (iRet <= 0)
    {
        return -1;
    }
    
    ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;
    ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;
    ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;
    ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565)? 16 : \
                                          0;
    ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;
    ptVideoBuf->tPixelDatas.iTotalBytes   = iRet;
    ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[0];    
    
    return 0;
}

/*
 * @description		       : 读写方式释放一帧图像数据
 * @param - ptVideoDevice  : 设备描述结构体
 * @param - ptVideoBuf     : Video图像数据结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    return 0;
}
/*
 * @description		       : 开始视频采集
 * @param - ptVideoDevice  : 设备描述结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2StartDevice(PT_VideoDevice ptVideoDevice)
{
    int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int iError;

    iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);
    if (iError) 
    {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to start capture.\n");
    	return -1;
    }
    return 0;
}
/*
 * @description		       : 停止视频采集
 * @param - ptVideoDevice  : 设备描述结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
{
    int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int iError;

    iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMOFF, &iType);
    if (iError) 
    {
    	DBG_PRINTF(DLOG_LVL_ERROR,DLOG_TAG,"Unable to stop capture.\n");
    	return -1;
    }
    return 0;
}
/*
 * @description		       : 得到图像格式
 * @param - ptVideoDevice  : 设备描述结构体
 * @return 			       : 0 成功;其他 失败
 */
static int V4l2GetFormat(PT_VideoDevice ptVideoDevice)
{
    return ptVideoDevice->iPixelFormat;
}


/* 构造一个VideoOpr结构体 */
static T_VideoOpr g_tV4l2VideoOpr = {
    .name        = "v4l2",
    .InitDevice  = V4l2InitDevice,
    .ExitDevice  = V4l2ExitDevice,
    .GetFormat   = V4l2GetFormat,
    .GetFrame    = V4l2GetFrameForStreaming,
    .PutFrame    = V4l2PutFrameForStreaming,
    .StartDevice = V4l2StartDevice,
    .StopDevice  = V4l2StopDevice,
};

/* 注册这个结构体 */
int V4l2Init(void)
{
    return RegisterVideoOpr(&g_tV4l2VideoOpr);
}
